package main.java.FSMDE.views;

import java.awt.Point;
import java.awt.Rectangle;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferedImage;
import java.awt.print.PageFormat;
import java.awt.print.Paper;
import java.awt.print.PrinterException;
import java.awt.print.PrinterJob;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.TooManyListenersException;
import java.util.UUID;

import javax.swing.JButton;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.xml.parsers.ParserConfigurationException;

import main.java.ERMDE.controllers.IProjectController;
import main.java.FSMDE.controllers.IDiagramController;
import main.java.FSMDE.controllers.DiagramController.CellConstants;
import main.java.FSMDE.controllers.listeners.IDiagramEventListener;
import main.java.FSMDE.models.Interface;

import com.jgoodies.forms.factories.FormFactory;
import com.jgoodies.forms.layout.ColumnSpec;
import com.jgoodies.forms.layout.FormLayout;
import com.jgoodies.forms.layout.RowSpec;
import com.mxgraph.io.mxCodec;
import com.mxgraph.model.mxCell;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.util.mxCellRenderer;
import com.mxgraph.util.mxResources;
import com.mxgraph.util.mxXmlUtils;
import com.mxgraph.util.png.mxPngEncodeParam;
import com.mxgraph.util.png.mxPngImageEncoder;
import com.mxgraph.view.mxGraph;


public class DiagramView extends JPanel implements IDiagramView,
		DropTargetListener {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	private JButton btnInterface;
	private JButton btnTransition;
	private JButton btnSave;
	private JButton btnSubdiagram;
	private JButton btnPrint;
	private JButton btnExport;
	private JButton btnLegend;
	private IDiagramController diagramController;
	private JButton btnValidate;
	private JButton btnZoomIn;
	private JButton btnZoomOut;
	private mxGraphComponent graphComponent;

	public DiagramView() {

		setLayout(new FormLayout(new ColumnSpec[] {
				FormFactory.RELATED_GAP_COLSPEC, FormFactory.DEFAULT_COLSPEC,
				FormFactory.DEFAULT_COLSPEC, FormFactory.DEFAULT_COLSPEC,
				FormFactory.DEFAULT_COLSPEC, FormFactory.DEFAULT_COLSPEC,
				FormFactory.DEFAULT_COLSPEC, FormFactory.DEFAULT_COLSPEC,
				FormFactory.DEFAULT_COLSPEC, FormFactory.DEFAULT_COLSPEC,
				FormFactory.DEFAULT_COLSPEC, FormFactory.DEFAULT_COLSPEC,
				FormFactory.DEFAULT_COLSPEC, FormFactory.DEFAULT_COLSPEC, },
				new RowSpec[] { FormFactory.RELATED_GAP_ROWSPEC,
						FormFactory.DEFAULT_ROWSPEC,
						FormFactory.RELATED_GAP_ROWSPEC,
						RowSpec.decode("default:grow"), }));

		this.btnInterface = new JButton("Interface");
		add(this.btnInterface, "2, 2");

		this.btnTransition = new JButton("Transition");
		add(this.btnTransition, "3, 2");

		this.btnSave = new JButton("Save");
		add(this.btnSave, "4, 2");

		this.btnSubdiagram = new JButton("Sub-Diagram");
		add(this.btnSubdiagram, "5, 2");

		this.btnPrint = new JButton("Print");
		add(this.btnPrint, "6, 2");

		this.btnExport = new JButton("Export");
		add(this.btnExport, "7, 2");

		this.btnValidate = new JButton("Validate");
		add(this.btnValidate, "8, 2");

		this.btnLegend = new JButton("Legend");
		add(this.btnLegend, "9, 2");

		btnZoomIn = new JButton("+");
		add(btnZoomIn, "10, 2");

		btnZoomOut = new JButton("-");
		add(btnZoomOut, "11, 2");

		updateUI();
	}

	@Override
	public void setController(IDiagramController controller) {
		this.diagramController = controller;

		this.graphComponent = new mxGraphComponent(
				this.diagramController.getGraph());
		this.graphComponent.setAutoScroll(true);
		this.graphComponent.setAutoscrolls(true);
		// cannot create new arrows clicking from entity
		this.graphComponent.getConnectionHandler().setCreateTarget(false);
		this.graphComponent.setConnectable(false);

		this.graphComponent.addMouseWheelListener(new MouseWheelListener() {
			public void mouseWheelMoved(MouseWheelEvent e) {
				// System.out.println("H");
				if (e.getWheelRotation() < 0) {
					if (diagramController.canDoZoomIn()) {
						diagramController.didZoomIn();
						graphComponent.zoomIn();
					}
				} else {
					if (diagramController.canDoZoomOut()) {
						diagramController.didZoomOut();
						graphComponent.zoomOut();
					}
				}
			}
		});
		MouseListener listener = new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				if (diagramController.hasPendingInterface()) {
					try {
						diagramController.addInterface(e.getPoint().x,
								e.getPoint().y);
					} catch (Exception e1) {
						e1.printStackTrace();
					}
				} else if (diagramController.hasPendingTransition()) {
					try {
						diagramController.addTransition(e.getPoint().x,
								e.getPoint().y);
					} catch (Exception e1) {
						e1.printStackTrace();
					}
				} else if (e.getClickCount() == 2) {
					Point mousePointClicked = e.getPoint();
					for (mxCell mx : diagramController.getAllInterfaceCells()) {
						Rectangle cellRectangle = mx.getGeometry()
								.getRectangle();
						if (cellRectangle.contains(mousePointClicked)) {
							String idCell = mx.getId();
							UUID interfaceId = UUID.fromString(idCell
									.substring(CellConstants.InterfacePrefix
											.length(), idCell.length()));
							for (Interface iface : diagramController
									.getAvailableInterfaces()) {
								if (interfaceId.equals(iface.getId())) {
									if (iface.hasSubDiagram()) {
										changeToSubdiagramView(iface
												.getRelatedSubdiagramName());
									}
								}
							}
						}
					}
				}
			}
		};

		this.graphComponent.getGraphControl().addMouseListener(listener);

		try {
			this.graphComponent.getDropTarget().addDropTargetListener(this);
		} catch (TooManyListenersException e1) {
			// should not occur
			e1.printStackTrace();
		}

		this.add(this.graphComponent, "2, 4, 12, 1, fill, fill");

		this.btnInterface.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				diagramController.createInterface();
			}
		});

		this.btnTransition.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				diagramController.createTransition();
			}
		});

		this.btnLegend.addMouseListener(new MouseAdapter() {

			@Override
			public void mouseClicked(MouseEvent e) {
				diagramController.toggleLegend();
			}
		});

		this.btnSave.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				diagramController.hideLegend();
				try {
					diagramController.save();
					JOptionPane.showMessageDialog(null, "Diagram saved",
							"Save Diagram", JOptionPane.INFORMATION_MESSAGE);
				} catch (ParserConfigurationException exception) {
					exception.printStackTrace();
				}
			}
		});

		this.btnSubdiagram.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {

				if (diagramController.getInterfacesCount() > 0) {
					String diagramName = JOptionPane.showInputDialog(null,
							"Provide the diagram's name", "New Diagram",
							JOptionPane.QUESTION_MESSAGE);
					while (diagramName.equals("")) {
						JOptionPane
								.showMessageDialog(
										null,
										"The subdiagram must have a name. Please provide one.",
										"Subdiagram without a name",
										JOptionPane.WARNING_MESSAGE);
						diagramName = JOptionPane.showInputDialog(null,
								"Provide the diagram's name", "New Diagram",
								JOptionPane.QUESTION_MESSAGE);
						if (diagramName == null) {
							break;
						}
					}
					if (diagramName != null) {

						String[] interfacesCombo = obtainInterfacesToSelect();
						if (interfacesCombo != null) {
							String interfaceChosen = (String) JOptionPane
									.showInputDialog(
											null,
											"Choose the interface related to the subdiagram",
											"Interface Selection",
											JOptionPane.QUESTION_MESSAGE, null,
											interfacesCombo, interfacesCombo[0]);
							if (interfaceChosen != null) {
								diagramController.createSubDiagram(diagramName,
										interfaceChosen);
							}
						} else {
							JOptionPane
									.showMessageDialog(
											null,
											"Every Interface must be associated with at most 1 Subdiagram.\n"
													+ "The Interfaces created so far are associated with Subdiagrams.\n"
													+ "Please, create an Interface before creating another Subdiagram.",
											"No free Interfaces",
											JOptionPane.WARNING_MESSAGE);
						}
					}
				} else {
					JOptionPane
							.showMessageDialog(
									null,
									"A Sub-Diagram must be related to an Interface.\nThere are no Interfaces created.",
									"No Interfaces",
									JOptionPane.WARNING_MESSAGE);
				}
			}

		});

		this.btnPrint.addActionListener(new PrintAction());

		this.btnExport.addActionListener(new ExportAction());

		this.btnValidate.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				diagramController.validate();
			}
		});

		this.btnZoomIn.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				if (diagramController.canDoZoomIn()) {
					diagramController.didZoomIn();
					graphComponent.zoomIn();
				}
			}
		});

		this.btnZoomOut.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseClicked(MouseEvent e) {
				if (diagramController.canDoZoomOut()) {
					diagramController.didZoomOut();
					graphComponent.zoomOut();
				}
			}
		});
	}

	private void changeToSubdiagramView(String subdiagramName) {
		Iterator<IDiagramEventListener> it = diagramController.getListeners();

		IProjectController projectController = null;
		while (it.hasNext()) {
			IDiagramEventListener listener = it.next();
			if (listener instanceof IProjectController) {
				projectController = (IProjectController) listener;
				projectController.changeToSubdiagram(subdiagramName);
				break;
			}
		}
	}

	@Override
	public void refreshGraphComponent() {
		this.graphComponent.refresh();
	}

	private String[] obtainInterfacesToSelect() {

		int availableInterfaces = this.diagramController
				.getInterfacesAvailableForSubdiagramCount();
		String[] interfaces = null;

		if (availableInterfaces > 0) {
			interfaces = new String[availableInterfaces];
			int index = 0;
			Iterator<Interface> iteratorInterface = this.diagramController
					.getAvailableInterfaces().iterator();
			while (iteratorInterface.hasNext()) {
				Interface iface = (Interface) iteratorInterface.next();
				if (!iface.hasSubDiagram()) {
					interfaces[index] = iface.getName();
					index++;
				}
			}
		}

		return interfaces;
	}

	@Override
	public boolean showDeleteDialog(String typeAndName, String otherMessege,
			boolean couldDelete) {
		int result = -1;
		if (couldDelete == false)
			JOptionPane.showMessageDialog(null, "The selected " + typeAndName
					+ otherMessege, "Deleting invalid " + typeAndName,
					JOptionPane.QUESTION_MESSAGE);
		else
			result = JOptionPane.showConfirmDialog(null, "The '" + typeAndName
					+ "' is being deleted, are you sure you want to do this?"
					+ otherMessege, "Deleting " + typeAndName,
					JOptionPane.YES_NO_OPTION);

		if (result == JOptionPane.YES_OPTION) {
			return true;
		}
		return false;
	}

	@Override
	public void dragEnter(DropTargetDragEvent dtde) {
	}

	@Override
	public void dragExit(DropTargetEvent dte) {
	}

	@Override
	public void dragOver(DropTargetDragEvent dtde) {
	}

	@Override
	public void drop(DropTargetDropEvent dtde) {
	}

	@Override
	public void dropActionChanged(DropTargetDragEvent dtde) {
	}

	private class ExportAction implements ActionListener {

		public void actionPerformed(ActionEvent e) {
			mxGraph graph = graphComponent.getGraph();

			// Creates the image for the PNG file
			BufferedImage image = mxCellRenderer.createBufferedImage(graph,
					null, 1, graphComponent.getBackground(),
					graphComponent.isAntiAlias(), null,
					graphComponent.getCanvas());

			// Creates the URL-encoded XML data
			mxCodec codec = new mxCodec();
			String xml;
			FileOutputStream outputStream = null;
			try {
				xml = URLEncoder.encode(
						mxXmlUtils.getXml(codec.encode(graph.getModel())),
						"UTF-8");

				mxPngEncodeParam param = mxPngEncodeParam
						.getDefaultEncodeParam(image);
				param.setCompressedText(new String[] { "mxGraphModel", xml });

				// Saves as a PNG file
				String name = diagramController.getDiagram().getName();
				outputStream = new FileOutputStream(new File(
						diagramController.getDataDirectory() + name + ".png"));

				mxPngImageEncoder encoder = new mxPngImageEncoder(outputStream,
						param);

				if (image != null) {
					encoder.encode(image);
					JOptionPane.showMessageDialog(null,
							"Diagram '" + name + "' exported as .png on "
									+ diagramController.getDataDirectory(),
							"Export Diagram", JOptionPane.INFORMATION_MESSAGE);

				} else {
					JOptionPane.showMessageDialog(graphComponent,
							mxResources.get("noImageData"));
				}
			} catch (Exception e1) {
				e1.printStackTrace();
			} finally {
				try {
					outputStream.close();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
			}
		}
	}

	private class PrintAction implements ActionListener {

		@Override
		public void actionPerformed(ActionEvent e) {

			PrinterJob pj = PrinterJob.getPrinterJob();
			PageFormat format = new PageFormat();
			format = pj.pageDialog(format);
			if (pj.printDialog()) {
				PageFormat pf = graphComponent.getPageFormat();
				Paper paper = new Paper();
				double margin = 36;
				paper.setImageableArea(margin, margin, paper.getWidth()
						- margin * 2, paper.getHeight() - margin * 2);
				pf.setPaper(paper);
				pj.setPrintable(graphComponent, pf);

				try {
					pj.print();
				} catch (PrinterException e2) {
					System.out.println(e2);
				}
			}
		}
	}
}
